﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Windows.Threading;
using Irony.Parsing;
using Peter.Common.Code;

namespace Peter.Logic.IronyGrammers
{
   /// <summary>
   /// Parses the grammar tree for C#
   /// </summary>
   [Export (typeof (IGrammarTreeParser))]
   [ExportMetadata ("Extensions", ".cs")]
   public class CSharpGrammarTreeParser : IGrammarTreeParser
   {
      private Dispatcher m_Dispatcher;
      private Collection<BaseCodeItem> m_CodeStructure;

      /// <summary>
      /// Called to parse the given tree. This is called from a worker thread.
      /// </summary>
      /// <param name="tree">Tree to parse</param>
      /// <param name="dispatcher">Thread dispatcher.</param>
      public IEnumerable<BaseCodeItem> ParseTree (ParseTree tree, Dispatcher dispatcher)
      {
         this.m_Dispatcher = dispatcher;
         this.m_CodeStructure = new Collection<BaseCodeItem> ();
         this.TraverseNodes (tree.Root);
         return this.m_CodeStructure;
      }

      /// <summary>
      /// Traverses all the child nodes in the given node.
      /// </summary>
      /// <param name="parseTreeNode">Node to traverse.</param>
      private void TraverseNodes (ParseTreeNode parseTreeNode)
      {
         foreach (var childNode in parseTreeNode.ChildNodes)
         {
            var nodeName = childNode.ToString ();
            if (nodeName == "property_declaration")
            {
               this.CreatePropertyItem (childNode);
            }
            else if (nodeName == "field_declaration")
            {
               this.CreateFieldNode (childNode);
            }
            else if (nodeName == "method_declaration")
            {
               this.CreateMethodItem (childNode);
            }
            else if (nodeName == "constructor_declaration")
            {
               //this.CreateConstructorNode (childNode);
            }
            else if (nodeName == "class_declaration")
            {
               // Do we want to group on classes?
               this.TraverseNodes (childNode);
            }
            else
            {
               this.TraverseNodes (childNode);
            }
         }
      }

      private void CreateFieldNode (ParseTreeNode node)
      {
         CSharpCodeItem codeItem = null;
         this.m_Dispatcher.Invoke (
            new Action (() => codeItem = new CSharpCodeItem (ObjectType.Field, node.Span.Location.Position)), null);
         while (codeItem == null)
            Thread.Sleep (5);

         ExtractModifiers (codeItem, node.ChildNodes[0].ChildNodes[1]);
         var returnType = node.ChildNodes[1].ChildNodes[0].ChildNodes[0];
         ExtractReturnType (codeItem, returnType);
         codeItem.Text = node.ChildNodes [2].ChildNodes [0].ChildNodes [0].Token.ValueString;

         this.m_CodeStructure.Add (codeItem);
      }

      /// <summary>
      /// Creates a property node and adds it to the structure.
      /// </summary>
      /// <param name="node">Parse node to get property from.</param>
      private void CreatePropertyItem (ParseTreeNode node)
      {
         CSharpCodeItem codeItem = null;
         this.m_Dispatcher.Invoke (
            new Action (() => codeItem = new CSharpCodeItem (ObjectType.Property, node.Span.Location.Position)), null);
         while (codeItem == null)
            Thread.Sleep (5);

         ExtractModifiers (codeItem, node.ChildNodes[0].ChildNodes[1]);
         // Return type
         var returnType = node.ChildNodes[1].ChildNodes[0].ChildNodes[0];
         ExtractReturnType (codeItem, returnType);
         ExtractName (codeItem, node.ChildNodes[2]);

         // Accessability (get; set;)

         this.m_CodeStructure.Add (codeItem);
      }

      /// <summary>
      /// Creates a property node and adds it to the structure.
      /// </summary>
      /// <param name="node">Parse node to get property from.</param>
      private void CreateMethodItem (ParseTreeNode node)
      {
         CSharpCodeItem codeItem = null;
         this.m_Dispatcher.Invoke (
            new Action (() => codeItem = new CSharpCodeItem (ObjectType.Method, node.Span.Location.Position)), null);
         while (codeItem == null)
            Thread.Sleep (5);

         ExtractModifiers (codeItem, node.ChildNodes[0].ChildNodes[1]);
         var returnType = node.ChildNodes[1].ChildNodes[0].ChildNodes[0];
         ExtractReturnType (codeItem, returnType);
         ExtractName (codeItem, node.ChildNodes[2]);

         this.m_CodeStructure.Add (codeItem);
      }

      /// <summary>
      /// Gets the modifiers out of the parse tree node.
      /// </summary>
      /// <param name="codeItem">Code item to add modifiers to.</param>
      /// <param name="modifiers">The parse tree node with modifier information.</param>
      private static void ExtractModifiers (CSharpCodeItem codeItem, ParseTreeNode modifiers)
      {
         codeItem.SetComments (modifiers.Comments.Aggregate (string.Empty,
                                                             (current, comment) =>
                                                             current + comment.ValueString.TrimStart ('/')).Trim ());
         foreach (var childNode in modifiers.ChildNodes)
         {
            codeItem.Modifiers.Add (childNode.ChildNodes [0].Term.ToString ());
         }
      }

      /// <summary>
      /// Gets the return type out of the parse tree node.
      /// </summary>
      /// <param name="codeItem">Code item to add modifiers to.</param>
      /// <param name="returnType">The parse tree node with return type information.</param>
      private static void ExtractReturnType (CSharpCodeItem codeItem, ParseTreeNode returnType)
      {
         if (returnType.Token != null && returnType.Token.ValueString == "void")
         {
            codeItem.ReturnType = "void";
            return;
         }

         if (returnType.ChildNodes[0].ChildNodes[0].Token != null)
         {
            codeItem.ReturnType = returnType.ChildNodes [0].ChildNodes [0].Token.ValueString;
            foreach (var childNode in returnType.ChildNodes [1].ChildNodes)
            {
               foreach (var subNode in childNode.ChildNodes)
               {
                  codeItem.ReturnType += subNode.Token.ValueString;
               }
            }
         }
         else
         {
            codeItem.ReturnType = FirstNonNull (returnType.ChildNodes [0]);
         }
      }

      private static string FirstNonNull (ParseTreeNode node)
      {
         if (node.Token != null)
            return node.Token.ValueString;
         return node.ChildNodes.Count > 0 ? FirstNonNull (node.ChildNodes [0]) : string.Empty;
      }

      /// <summary>
      /// Gets the name out of the parse tree node.
      /// </summary>
      /// <param name="codeItem">Code item to add modifiers to.</param>
      /// <param name="name">The parse tree node with name type information.</param>
      private static void ExtractName (CSharpCodeItem codeItem, ParseTreeNode name)
      {
         codeItem.Text = name.ChildNodes[0].ChildNodes[0].Token.ValueString;
         foreach (var childNode in name.ChildNodes[1].ChildNodes)
         {
            foreach (var subNode in childNode.ChildNodes)
            {
               codeItem.Text += subNode.Token.ValueString;
            }
         }
      }
   }
}
